ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / .specify / extensions / review / scripts / bash / detect-changed-files.sh
Displaying Raw β’ Download
.specify/extensions/review/scripts/bash/detect-changed-files.sh 4e48e64e786b58d7c73280f7aa6ebf55be9e53e5 (4e48e64e) Text, 7.20 KB
T8b949e#!/usr/bin/env bash
T8b949e# Detect changed files for code review via git diff
T8b949e#
T8b949e# Identifies changed files by comparing the current branch against
T8b949e# the default branch plus any uncommitted work (Mode A β feature branch
T8b949e# diff + working directory) or by collecting staged + unstaged changes
T8b949e# (Mode B β working directory changes only).
T8b949e#
T8b949e# Usage: ./detect-changed-files.sh [OPTIONS]
T8b949e#
T8b949e# OPTIONS:
T8b949e# --json Output in JSON format (for machine consumption)
T8b949e# --help, -h Show this help message
T8b949e#
T8b949e# EXIT CODES:
T8b949e# 0 Changed files detected successfully
T8b949e# 1 Error (git unavailable, not a git repository)
T8b949e# 2 No changes detected
T8b949e#
T8b949e# OUTPUTS:
T8b949e# Text mode:
T8b949e# BRANCH: <current-branch>
T8b949e# DEFAULT_BRANCH: <default-branch>
T8b949e# MODE: <detection mode description>
T8b949e# CHANGED_FILES:
T8b949e# file1
T8b949e# file2
T8b949e#
T8b949e# JSON mode:
T8b949e# {"branch":"...","default_branch":"...","mode":"...","changed_files":["..."]}
Tffa657set -e
T8b949e# --- Argument parsing ---
Te6edf3JSON_MODETff7b72=Tffa657false
Tff7b72for arg Tff7b72in Ta5d6ff"Te6edf3$@Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72case Ta5d6ff"Te6edf3$argTa5d6ff" Tff7b72in
--jsonTff7b72) Te6edf3JSON_MODETff7b72=Tffa657true Tb4b4b4;Tb4b4b4;
--helpTb4b4b4|-hTff7b72)
cat Ta5d6ff<< 'EOF'
Usage: detect-changed-files.sh [OPTIONS]
Detect changed files for code review via git diff.
OPTIONS:
--json Output in JSON format
--help, -h Show this help message
EXIT CODES:
0 Changed files detected successfully
1 Error (git unavailable, not a git repository)
2 No changes detected
EOF
Tffa657exit T79c0ff0
Tb4b4b4;Tb4b4b4;
*Tff7b72) Tffa657echo Ta5d6ff"Ta5d6ffERROR: Unknown option 'Te6edf3$argTa5d6ff'Ta5d6ff" >Tb4b4b4&T79c0ff2Tb4b4b4; Tffa657exit T79c0ff1 Tb4b4b4;Tb4b4b4;
Tff7b72esac
Tff7b72done
T8b949e# --- Helper: escape a string for safe JSON embedding ---
json_escapeTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3sTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Te6edf3sTff7b72=Ta5d6ff"Tffd700${Te6edf3sTb4b4b4//Tffea00\\Tb4b4b4/Tffea00\\Tffea00\\Tffd700}Ta5d6ff" T8b949e# \ β \\
Te6edf3sTff7b72=Ta5d6ff"Tffd700${Te6edf3sTb4b4b4//Tffea00\"Tb4b4b4/Tffea00\\Tffea00\"Tffd700}Ta5d6ff" T8b949e# " β \\"
Te6edf3sTff7b72=Ta5d6ff"Tffd700${Te6edf3sTb4b4b4//Ta5d6ff$'\t'Tb4b4b4/Tffea00\\Te6edf3tTffd700}Ta5d6ff" T8b949e# tab β \t
Te6edf3sTff7b72=Ta5d6ff"Tffd700${Te6edf3sTb4b4b4//Ta5d6ff$'\n'Tb4b4b4/Tffea00\\Te6edf3nTffd700}Ta5d6ff" T8b949e# newline β \n
Te6edf3sTff7b72=Ta5d6ff"Tffd700${Te6edf3sTb4b4b4//Ta5d6ff$'\r'Tb4b4b4/Tffea00\\Te6edf3rTffd700}Ta5d6ff" T8b949e# carriage return β \r
Tffa657printf Ta5d6ff'%s' Ta5d6ff"Te6edf3$sTa5d6ff"
Tff7b72}
T8b949e# --- Helper: format bash array as JSON array ---
fmt_arrayTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3arrTff7b72=Tff7b72(Ta5d6ff"Te6edf3$@Ta5d6ff"Tff7b72)
Tff7b72if Tff7b72[Tff7b72[ Tffd700${#Te6edf3arrTb4b4b4[@]Tffd700} -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then Tffa657echo Ta5d6ff"[]"Tb4b4b4; Tff7b72returnTb4b4b4; Tff7b72fi
Tffa657local Te6edf3firstTff7b72=Tffa657true
Tffa657local Te6edf3resultTff7b72=Ta5d6ff"["
Tff7b72for item Tff7b72in Ta5d6ff"Tffd700${Te6edf3arrTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72if Te6edf3$firstTb4b4b4; Tff7b72then Te6edf3firstTff7b72=falseTb4b4b4; Tff7b72else Te6edf3resultTff7b72+=Ta5d6ff","Tb4b4b4; Tff7b72fi
Te6edf3resultTff7b72+=Ta5d6ff"Ta5d6ff\"Tff7b72$(json_escape Ta5d6ff"Te6edf3$itemTa5d6ff"Tff7b72)Ta5d6ff\"Ta5d6ff"
Tff7b72done
Te6edf3resultTff7b72+=Ta5d6ff"]"
Tffa657echo Ta5d6ff"Te6edf3$resultTa5d6ff"
Tff7b72}
T8b949e# --- Helper: output error and exit ---
error_exitTff7b72(Tff7b72) Tff7b72{
Tffa657local Te6edf3messageTff7b72=Ta5d6ff"Te6edf3$1Ta5d6ff"
Tffa657local Te6edf3codeTff7b72=Ta5d6ff"Tffd700${Te6edf32Tff7b72:-Te6edf31Tffd700}Ta5d6ff"
Tff7b72if Te6edf3$JSON_MODETb4b4b4; Tff7b72then
Tffa657printf Ta5d6ff'{"error":"%s"}\n' Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$messageTa5d6ff"Tff7b72)Ta5d6ff"
Tff7b72else
Tffa657echo Ta5d6ff"Ta5d6ffError: Te6edf3$messageTa5d6ff" >Tb4b4b4&T79c0ff2
Tff7b72fi
Tffa657exit Ta5d6ff"Te6edf3$codeTa5d6ff"
Tff7b72}
T8b949e# --- 1a. Verify Git Availability ---
Tff7b72if ! Tffa657command -v git >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
error_exit Ta5d6ff"git is not available. The review extension requires git to identify changed files." T79c0ff1
Tff7b72fi
Tff7b72if ! git rev-parse --git-dir >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
error_exit Ta5d6ff"Not a git repository. The review extension requires git to identify changed files." T79c0ff1
Tff7b72fi
T8b949e# --- 1b. Detect Branch Context ---
T8b949e# Get current branch (empty string if detached HEAD)
Te6edf3CURRENT_BRANCHTff7b72=Tff7b72$(git branch --show-current T79c0ff2>/dev/null Tff7b72|| Tffa657echo Ta5d6ff""Tff7b72)
T8b949e# Determine default branch
Te6edf3DEFAULT_BRANCHTff7b72=Ta5d6ff""
T8b949e# Try symbolic-ref first
Te6edf3symrefTff7b72=Tff7b72$(git symbolic-ref refs/remotes/origin/HEAD T79c0ff2>/dev/null Tff7b72|| Tffa657echo Ta5d6ff""Tff7b72)
Tff7b72if Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$symrefTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3DEFAULT_BRANCHTff7b72=Ta5d6ff"Tffd700${Te6edf3symrefTb4b4b4##refs/remotes/origin/Tffd700}Ta5d6ff"
Tff7b72fi
T8b949e# Fallback: check origin/main
Tff7b72if Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if git rev-parse --verify origin/main >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Te6edf3DEFAULT_BRANCHTff7b72=Ta5d6ff"main"
Tff7b72fi
Tff7b72fi
T8b949e# Fallback: check origin/master
Tff7b72if Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if git rev-parse --verify origin/master >/dev/null T79c0ff2>Tb4b4b4&T79c0ff1Tb4b4b4; Tff7b72then
Te6edf3DEFAULT_BRANCHTff7b72=Ta5d6ff"master"
Tff7b72fi
Tff7b72fi
T8b949e# --- 1c. Get Changed Files ---
Te6edf3CHANGED_FILESTff7b72=Tff7b72(Tff7b72)
Te6edf3MODETff7b72=Ta5d6ff""
Tff7b72if Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$CURRENT_BRANCHTa5d6ff" Tff7b72&& -n Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff" Tff7b72&& Ta5d6ff"Te6edf3$CURRENT_BRANCHTa5d6ff" !Tff7b72= Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Mode A β Feature Branch
Te6edf3MERGE_BASETff7b72=Tff7b72$(git merge-base Ta5d6ff"Ta5d6fforigin/Te6edf3$DEFAULT_BRANCHTa5d6ff" HEAD T79c0ff2>/dev/null Tff7b72|| Tffa657echo Ta5d6ff""Tff7b72)
Tff7b72if Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$MERGE_BASETa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Committed changes since merge-base
Te6edf3COMMITTEDTff7b72=Tff7b72(Tff7b72)
Tff7b72while Te6edf3IFSTff7b72= Tffa657read -r -d Ta5d6ff'' lineTb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$lineTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Te6edf3COMMITTEDTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$lineTa5d6ff"Tff7b72)
Tff7b72done < <Tff7b72(git diff --name-only -z --diff-filterTff7b72=ACMR Ta5d6ff"Tffd700${Te6edf3MERGE_BASETffd700}Ta5d6ff...HEADTa5d6ff" T79c0ff2>/dev/nullTff7b72)
T8b949e# Staged (index) changes
Te6edf3STAGEDTff7b72=Tff7b72(Tff7b72)
Tff7b72while Te6edf3IFSTff7b72= Tffa657read -r -d Ta5d6ff'' lineTb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$lineTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Te6edf3STAGEDTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$lineTa5d6ff"Tff7b72)
Tff7b72done < <Tff7b72(git diff --cached --name-only -z --diff-filterTff7b72=ACMR T79c0ff2>/dev/nullTff7b72)
T8b949e# Unstaged (working tree) changes
Te6edf3UNSTAGEDTff7b72=Tff7b72(Tff7b72)
Tff7b72while Te6edf3IFSTff7b72= Tffa657read -r -d Ta5d6ff'' lineTb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$lineTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Te6edf3UNSTAGEDTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$lineTa5d6ff"Tff7b72)
Tff7b72done < <Tff7b72(git diff --name-only -z --diff-filterTff7b72=ACMR T79c0ff2>/dev/nullTff7b72)
T8b949e# Combine and deduplicate (bash 3 compatible β no associative arrays)
Te6edf3CHANGED_FILESTff7b72=Tff7b72(Tff7b72)
Tff7b72for f Tff7b72in Ta5d6ff"Tffd700${Te6edf3COMMITTEDTb4b4b4[@]Tffd700}Ta5d6ff" Ta5d6ff"Tffd700${Te6edf3STAGEDTb4b4b4[@]Tffd700}Ta5d6ff" Ta5d6ff"Tffd700${Te6edf3UNSTAGEDTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$fTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Tff7b72continue
Te6edf3_dupTff7b72=Tffa657false
Tff7b72for existing Tff7b72in Ta5d6ff"Tffd700${Te6edf3CHANGED_FILESTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$existingTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"Te6edf3$fTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3_dupTff7b72=Tffa657true
Tff7b72break
Tff7b72fi
Tff7b72done
Tff7b72if ! Te6edf3$_dupTb4b4b4; Tff7b72then
Te6edf3CHANGED_FILESTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$fTa5d6ff"Tff7b72)
Tff7b72fi
Tff7b72done
Te6edf3MODETff7b72=Ta5d6ff"Ta5d6ffFeature branch diff (Tffd700${Te6edf3DEFAULT_BRANCHTffd700}Ta5d6ff...HEAD) + uncommitted changesTa5d6ff"
Tff7b72else
T8b949e# merge-base failed β fall through to Mode B
Te6edf3DEFAULT_BRANCHTff7b72=Ta5d6ff""
Tff7b72fi
Tff7b72fi
Tff7b72if Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$MODETa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
T8b949e# Mode B β Working Directory Changes
Te6edf3STAGEDTff7b72=Tff7b72(Tff7b72)
Tff7b72while Te6edf3IFSTff7b72= Tffa657read -r -d Ta5d6ff'' lineTb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$lineTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Te6edf3STAGEDTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$lineTa5d6ff"Tff7b72)
Tff7b72done < <Tff7b72(git diff --cached --name-only -z --diff-filterTff7b72=ACMR T79c0ff2>/dev/nullTff7b72)
Te6edf3UNSTAGEDTff7b72=Tff7b72(Tff7b72)
Tff7b72while Te6edf3IFSTff7b72= Tffa657read -r -d Ta5d6ff'' lineTb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -n Ta5d6ff"Te6edf3$lineTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Te6edf3UNSTAGEDTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$lineTa5d6ff"Tff7b72)
Tff7b72done < <Tff7b72(git diff --name-only -z --diff-filterTff7b72=ACMR T79c0ff2>/dev/nullTff7b72)
T8b949e# Combine and deduplicate (bash 3 compatible β no associative arrays)
Te6edf3CHANGED_FILESTff7b72=Tff7b72(Tff7b72)
Tff7b72for f Tff7b72in Ta5d6ff"Tffd700${Te6edf3STAGEDTb4b4b4[@]Tffd700}Ta5d6ff" Ta5d6ff"Tffd700${Te6edf3UNSTAGEDTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$fTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Tff7b72continue
Te6edf3_dupTff7b72=Tffa657false
Tff7b72for existing Tff7b72in Ta5d6ff"Tffd700${Te6edf3CHANGED_FILESTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tff7b72if Tff7b72[Tff7b72[ Ta5d6ff"Te6edf3$existingTa5d6ff" Tff7b72=Tff7b72= Ta5d6ff"Te6edf3$fTa5d6ff" Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Te6edf3_dupTff7b72=Tffa657true
Tff7b72break
Tff7b72fi
Tff7b72done
Tff7b72if ! Te6edf3$_dupTb4b4b4; Tff7b72then
Te6edf3CHANGED_FILESTff7b72+=Tff7b72(Ta5d6ff"Te6edf3$fTa5d6ff"Tff7b72)
Tff7b72fi
Tff7b72done
Te6edf3MODETff7b72=Ta5d6ff"Working directory changes (staged + unstaged)"
Tff7b72[Tff7b72[ -z Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff" Tff7b72]Tff7b72] Tff7b72&& Te6edf3DEFAULT_BRANCHTff7b72=Ta5d6ff"(unknown)"
Tff7b72fi
T8b949e# --- 1d. Validate Changed Files ---
Tff7b72if Tff7b72[Tff7b72[ Tffd700${#Te6edf3CHANGED_FILESTb4b4b4[@]Tffd700} -eq T79c0ff0 Tff7b72]Tff7b72]Tb4b4b4; Tff7b72then
Tff7b72if Te6edf3$JSON_MODETb4b4b4; Tff7b72then
Tffa657printf Ta5d6ff'{"branch":"%s","default_branch":"%s","mode":"%s","changed_files":[],"message":"No changes detected. Nothing to review."}\n' Tffea00\
Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$CURRENT_BRANCHTa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$MODETa5d6ff"Tff7b72)Ta5d6ff"
Tff7b72else
Tffa657echo Ta5d6ff"No changes detected. Nothing to review."
Tff7b72fi
Tffa657exit T79c0ff2
Tff7b72fi
T8b949e# --- Output ---
Tff7b72if Te6edf3$JSON_MODETb4b4b4; Tff7b72then
Tffa657printf Ta5d6ff'{"branch":"%s","default_branch":"%s","mode":"%s","changed_files":%s}\n' Tffea00\
Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$CURRENT_BRANCHTa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$DEFAULT_BRANCHTa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(json_escape Ta5d6ff"Te6edf3$MODETa5d6ff"Tff7b72)Ta5d6ff" Ta5d6ff"Tff7b72$(fmt_array Ta5d6ff"Tffd700${Te6edf3CHANGED_FILESTb4b4b4[@]Tffd700}Ta5d6ff"Tff7b72)Ta5d6ff"
Tff7b72else
Tffa657echo Ta5d6ff"Ta5d6ffBRANCH: Te6edf3$CURRENT_BRANCHTa5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ffDEFAULT_BRANCH: Te6edf3$DEFAULT_BRANCHTa5d6ff"
Tffa657echo Ta5d6ff"Ta5d6ffMODE: Te6edf3$MODETa5d6ff"
Tffa657echo Ta5d6ff"CHANGED_FILES:"
Tff7b72for f Tff7b72in Ta5d6ff"Tffd700${Te6edf3CHANGED_FILESTb4b4b4[@]Tffd700}Ta5d6ff"Tb4b4b4; Tff7b72do
Tffa657echo Ta5d6ff"Ta5d6ff Te6edf3$fTa5d6ff"
Tff7b72done
Tff7b72fi
Tffa657exit T79c0ff0
Served by rngit 1.5.2 - Generated in 0.07s